﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using System.Collections;
using System.Collections.Generic;
using Tesla.Bounding;
using Tesla.Collections;
using Tesla.Content;
using Tesla.Core;
using Tesla.Graphics;

namespace Tesla.Scene {
    /// <summary>
    /// A node represents an internal element in the scene graph, meaning it is capable of owning children. They are responsible for updating
    /// and drawing said children. Although Nodes are purely logical and are not actually drawn, their world bounding contains all their children's,
    /// which is leveraged during the culling process to cull entire subtree's in the scene graph that are not visible.
    /// </summary>
    public class Node : Spatial, IEnumerable<Spatial> {
        private TeslaList<Spatial> _children;

        /// <summary>
        /// Gets the children list as read-only.
        /// </summary>
        public ReadOnlyList<Spatial> Children {
            get {
                return _children.AsReadOnly();
            }
        }

        /// <summary>
        /// Gets the number of child Spatials attached to this node.
        /// </summary>
        public int Count {
            get {
                return _children.Count;
            }
        }

        /// <summary>
        /// Gets a child at the index.
        /// </summary>
        /// <param name="index">Index of the child Spatial</param>
        /// <returns>The child, if it exists. Null if it does not, or the index was out of range.</returns>
        public Spatial this[int index] {
            get {
                return GetChildAt(index);
            }
        }

        /// <summary>
        /// Gets the first child with the specified name.
        /// </summary>
        /// <param name="name">Name of the child Spatial.</param>
        /// <returns>The child, if it exists. Null if it oes not or the name is null.</returns>
        public Spatial this[String name] {
            get {
                return GetChildNamed(name);
            }
        }

        /// <summary>
        /// Do not use directly, for ISavable
        /// </summary>
        protected Node() {}

        /// <summary>
        /// Creates a new instance of Node with initial child capacity of 1.
        /// </summary>
        /// <param name="name">Name of the node</param>
        public Node(String name)
            : base(name) {
                _children = new TeslaList<Spatial>(1);
        }

        /// <summary>
        /// Creates a new instance of Node with the specified name and initial child capacity.
        /// </summary>
        /// <param name="name">Name of the node</param>
        /// <param name="initialChildCapacity">Initial child capacity</param>
        public Node(String name, int initialChildCapacity) 
            : base(name) {
                _children = new TeslaList<Spatial>(initialChildCapacity);
        }

        /// <summary>
        /// Accept a visitor to execute on this node.
        /// </summary>
        /// <param name="visitor">Visitor</param>
        /// <param name="preexecute">True for the visitor is to perform its operation on the node before it visits its children</param>
        public override void AcceptVisitor(IVisitor visitor, bool preexecute) {
            if(preexecute) {
                visitor.Visit(this);
            }

            for(int i = 0; i < _children.Count; i++) {
                _children[i].AcceptVisitor(visitor, preexecute);
            }

            if(!preexecute) {
                visitor.Visit(this);
            }
        }

        /// <summary>
        /// Adds a child to this node.
        /// </summary>
        /// <param name="s">Spatial to be added</param>
        /// <returns>True if the operation succeeded</returns>
        public bool AddChild(Spatial s) {
            if(s != null && s.Parent != this) {
                s.RemoveFromParent();
                s.Parent = this;
                _children.Add(s);
                PropagateDirtyDown(DirtyMark.Transform | DirtyMark.Bounding | DirtyMark.Lighting);
                PropagateDirtyUp(DirtyMark.Bounding);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Gets the child at the specified index.
        /// </summary>
        /// <param name="index">Index of the child</param>
        /// <returns>The child if it exists, otherwise null if it does not or if the index is out of range</returns>
        public Spatial GetChildAt(int index) {
            if(index >= _children.Count || index < 0) {
                return null;
            } 

            return _children[index];
        }

        /// <summary>
        /// Gets the first child with the specified name, if it exists.
        /// </summary>
        /// <param name="name">Name to search</param>
        /// <returns>The first spatial with the name, or null if it does not exist</returns>
        public Spatial GetChildNamed(String name) {
            if (name == null) {
                return null;
            }

            for(int i = 0; i < _children.Count; i++) {
                Spatial child = _children[i];
                if(child.Name != null && child.Name.Equals(name)) {
                    return child;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets all children with the specified name.
        /// </summary>
        /// <param name="name">Name to search for</param>
        /// <returns>All children with that name.</returns>
        public Spatial[] GetAllChildrenNamed(String name) {
            if (name == null) {
                return new Spatial[0];
            }

            List<Spatial> list = new List<Spatial>();

            for(int i = 0; i < _children.Count; i++) {
                Spatial child = _children[i];
                if(child.Name != null && child.Name.Equals(name)) {
                    list.Add(child);
                }
            }
            return list.ToArray();
        } 

        /// <summary>
        /// Gets the first index of the specified child, if it exists.
        /// </summary>
        /// <param name="s">Child to get the index of</param>
        /// <returns>The child's index in the node's list, or -1 if it is not present</returns>
        public int GetChildIndex(Spatial s) {
            return _children.IndexOf(s);
        }

        /// <summary>
        /// Removes the child from the children
        /// list and sets its parent to null.
        /// </summary>
        /// <param name="s">Child to be removed</param>
        /// <returns>True if the operation succeeded</returns>
        public bool RemoveChild(Spatial s) {
            if(s != null && s.Parent == this) {
                _children.Remove(s);
                s.Parent = null;
                PropagateDirtyUp(DirtyMark.Bounding);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Removes the child from the children list at the specified index.
        /// </summary>
        /// <param name="index">Index of the child</param>
        /// <returns>True if the operation succeeded, will return false is index is out of range</returns>
        public bool RemoveChildAt(int index) {
            if(index >= _children.Count || index < 0) {
                return false;
            }

            Spatial child = _children[index];
            if(child != null) {
                child.Parent = null;
                _children.RemoveAt(index);
                PropagateDirtyUp(DirtyMark.Bounding);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Removes the first child with the specified name from this
        /// node.
        /// </summary>
        /// <param name="name">Name to search for</param>
        /// <returns>True if the operation succeeded</returns>
        public bool RemoveChildNamed(String name) {
            for(int i = 0; i < _children.Count; i++) {
                Spatial child = _children[i];
                if(child.Name.Equals(name)) {
                    return RemoveChildAt(i);
                }
            }
            return false;
        }

        /// <summary>
        /// Detaches all children from this node.
        /// </summary>
        public void RemoveAllChildren() {
            for(int i = 0; i < _children.Count; i++) {
                Spatial child = _children[i];
                if(child != null) {
                    child.Parent = null;
                }
            }
            _children.Clear();
            PropagateDirtyUp(DirtyMark.Bounding);
        }

        /// <summary>
        /// Gets a desendent with the specified name, if it exists.
        /// </summary>
        /// <param name="name">Name of descendent</param>
        /// <returns>The spatial, or null if it does not exist</returns>
        public Spatial GetDescendentNamed(String name) {
            Spatial s = this.GetChildNamed(name);
            if(s == null) {
                for(int i = 0; i < _children.Count; i++) {
                    Spatial child = _children[i];
                    if(child is Node) {
                        s = ((Node) child).GetDescendentNamed(name);
                        if(s != null) {
                            break;
                        }
                    }
                }
            }
            return s;
        }

        /// <summary>
        /// Propagates a dirty mark down the tree, calling PropagateDirtyDown()
        /// on each child.
        /// </summary>
        /// <param name="flag">Dirty mark</param>
        public override void PropagateDirtyDown(DirtyMark flag) {
            MarkDirty(flag);
            for(int i = 0; i < _children.Count; i++) {
                _children[i].PropagateDirtyDown(flag);
            }
        }

        /// <summary>
        /// Calls OnDraw() on each child for frustum check.
        /// </summary>
        /// <param name="renderer">Renderer</param>
        public override void Draw(IRenderer renderer) {
            for(int i = 0; i < _children.Count; i++) {
                _children[i].OnDraw(renderer);
            }
        }

        /// <summary>
        /// Update the Node. Updating it will update the transformation, lighting, and bounding properties
        /// if they are dirty. This is mainly used internally, as initiating will propagate the scene world bounding to the root node,
        /// after its children and their children are updated.
        /// 
        /// Update order:
        /// 
        /// 1. Controllers
        /// 2. World Transform
        /// 3. World lighting
        /// 4. Children
        /// 5. World Bounding
        /// 6. Propagate Bound (if initiator)
        /// </summary>
        /// <param name="time">Interpolation time</param>
        /// <param name="initiator">True if this node is the initiator (usually the root)</param>
        public override void Update(GameTime time, bool initiator) {
            UpdateControllers(time);

            if(IsDirty(DirtyMark.Transform)) {
                UpdateWorldTransform(false);
            }

            if(IsDirty(DirtyMark.Lighting)) {
                UpdateWorldLights(false);
            }

            for(int i = 0; i < _children.Count; i++) {
                _children[i].Update(time, false);
            }
            
            if(IsDirty(DirtyMark.Bounding)) {
                UpdateWorldBound(false);
                if(initiator) {
                    PropagateBoundToRoot();
                }
            }
        }

        /// <summary>
        /// Convienence method to propagate a render state down the scene graph.
        /// </summary>
        /// <param name="rs">Renderstate</param>
        public override void SetRenderState(RenderState rs) {
            for(int i = 0; i < _children.Count; i++) {
                _children[i].SetRenderState(rs);
            }
        }

        /// <summary>
        /// Updates the world transform of this Node, and if recurse is true, it updates
        /// the world transform of each child.
        /// </summary>
        /// <param name="recurse">True if the node should recursively update the world transforms of its children</param>
        public override void UpdateWorldTransform(bool recurse) {
            base.UpdateWorldTransform(recurse);
            if(recurse) {
                for(int i = 0; i < _children.Count; i++) {
                    _children[i].UpdateWorldTransform(true);
                }
            }
        }

        /// <summary>
        /// Updates the world bounding of this node. If recurse is true, the world boundings
        /// of each child are also updated.
        /// </summary>
        /// <param name="recurse">True if node should recursively update the world boundings of its children</param>
        public override void UpdateWorldBound(bool recurse) {
            //If no children, make sure the world bounding gets set to null
            if(_children.Count == 0) {
                base.WorldBounding = null;
                return;
            }

            bool firstChild = true;

            //Loop over each child, and set or clone the first child world bound for our own,
            //then merge it locally with the rest of child bounds
            for(int i = 0; i < _children.Count; i++) {
                Spatial child = _children[i];
                BoundingVolume childWorldBound = child.WorldBounding;
                if(recurse) {
                    child.UpdateWorldBound(true);
                }

                if(firstChild) {
                    if(base.WorldBounding != null) {
                        if(childWorldBound != null) {
                            base.WorldBounding.Set(childWorldBound);
                            //Got setup with the first child, now onto merging
                            firstChild = false;
                        }
                    } else {
                        if(childWorldBound != null) {
                            base.WorldBounding = childWorldBound.Clone();
                            //If the child's bound is null, we must keep looking
                            firstChild = false;
                        }
                    }
                } else {
                    if(childWorldBound != null) {
                        base.WorldBounding.MergeLocal(childWorldBound);
                    }
                }
            }
            ClearDirty(DirtyMark.Bounding);
        }

        /// <summary>
        /// Convienence method to propagate a model bounding volume down the scene graph. Each child that is a mesh
        /// gets a cloned copy.
        /// </summary>
        /// <param name="volume">Bounding Volume</param>
        public override void SetModelBound(BoundingVolume volume) {
            for(int i = 0; i < _children.Count; i++) {
                Spatial s = _children[i];
                if(s is Mesh) {
                    s.SetModelBound(volume.Clone());
                } else {
                    s.SetModelBound(volume);
                }
            }
        }

        /// <summary>
        /// Convience method to propagate a material down the scene graph. Each child that is a mesh
        /// gets a cloned copy.
        /// </summary>
        /// <param name="material">Material</param>
        public override void SetMaterial(Material material) {
            for(int i = 0; i < _children.Count; i++) {
                Spatial s = _children[i];
                if(s is Mesh) {
                    s.SetMaterial(material.Clone());
                } else {
                    s.SetMaterial(material);
                }
            }
        }

        /// <summary>
        /// Sorts child world lights.
        /// </summary>
        public override void SortLights() {
            for(int i = 0; i < _children.Count; i++) {
                _children[i].SortLights();
            }
        }

        /// <summary>
        /// Updates world lights.
        /// </summary>
        /// <param name="recurse">True if children should be updated</param>
        public override void UpdateWorldLights(bool recurse) {
            ClearDirty(DirtyMark.Lighting);
            if(recurse) {
                for(int i = 0; i < _children.Count; i++) {
                    _children[i].UpdateWorldLights(true);
                }
            }
        }

        /// <summary>
        /// Serializes the object and writes it to the output.
        /// </summary>
        /// <param name="output">Savable Output</param>
        public override void Write(ISavableWriter output) {
            base.Write(output);

            output.WriteSavable<Spatial>("Children", _children.ToArray());
        }

        /// <summary>
        /// Deserializes the object and populates it from the input.
        /// </summary>
        /// <param name="input">Savable input</param>
        public override void Read(ISavableReader input) {
            base.Read(input);

            Spatial[] spatials = input.ReadSavableArray<Spatial>();
            _children = new TeslaList<Spatial>();

            for(int i = 0; i < spatials.Length; i++) {
                Spatial spatial = spatials[i];
                if(spatial != null) {
                    spatial.Parent = this;
                    _children.Add(spatial);
                }
            }
        }

        /// <summary>
        /// Gets an enumerator for the node's children.
        /// </summary>
        /// <returns>Enumerator</returns>
        public IEnumerator<Spatial> GetEnumerator() {
            return _children.GetEnumerator();
        }

        /// <summary>
        /// Gets an enumerator for the node's children.
        /// </summary>
        /// <returns>Enumerator</returns>
        IEnumerator IEnumerable.GetEnumerator() {
            return _children.GetEnumerator();
        }
    }
}
